<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script async src="https://unpkg.com/es-module-shims@1.5.18/dist/es-module-shims.wasm.js"></script>
    <!-- for typescript target: AMD -->
    <script src="https://cdn.jsdelivr.net/gh/requirejs/requirejs/require.js"></script>
    <!--<script src="https://unpkg.com/@wu-component/web-core-plus@0.3.3/dist/index.es.js"></script>-->
    <script type="importmap" id="dependenciesMap">
        {
          "imports": {
            "@wu-component/web-core-plus": "https://unpkg.com/@wu-component/web-core-plus@latest/dist/index.es.js"
          }
        }
    </script>
    <script>
        (() => {
            let scriptEls = []

            window.process = { env: {} }
            window.__modules__ = {}

            window.__export__ = (mod, key, get) => {
                Object.defineProperty(mod, key, {
                    enumerable: true,
                    configurable: true,
                    get
                })
            }

            window.__dynamic_import__ = key => {
                return Promise.resolve(window.__modules__[key])
            }

            /**
             * 加载依赖
             * @param options
             */
            function load_dependencies(options) {
                const dom = document.querySelector("#dependenciesMap");
                const current = JSON.parse(dom.innerHTML)? JSON.parse(dom.innerHTML).imports: {}
                const newDependencies = Object.assign({}, current, options.dependencies || {});
                dom.innerHTML = JSON.stringify({
                    "imports": newDependencies
                })
                const newDom = dom.cloneNode(true);
                dom.remove()
                document.querySelector("head").appendChild(newDom);
            }

            async function handle_message(ev) {
                let { action, cmd_id } = ev.data;
                const send_message = (payload) => parent.postMessage( { ...payload }, ev.origin);
                const send_reply = (payload) => send_message({ ...payload, cmd_id });
                const send_ok = () => send_reply({ action: 'cmd_ok' });
                const send_error = (message, stack) => send_reply({ action: 'cmd_error', message, stack });

                if (action === 'eval') {
                    try {
                        if (scriptEls.length) {
                            scriptEls.forEach(el => {
                                document.head.removeChild(el)
                            })
                            scriptEls.length = 0
                        }

                        let { script: scripts } = ev.data.args
                        if (typeof scripts === 'string') scripts = [scripts]

                        for (const script of scripts) {
                            const scriptEl = document.createElement('script')
                            scriptEl.setAttribute('type', 'module')
                            // send ok in the module script to ensure sequential evaluation
                            // of multiple proxy.eval() calls
                            const done = new Promise((resolve) => {
                                window.__next__ = resolve
                            })
                            scriptEl.innerHTML = script + `\nwindow.__next__()`
                            document.head.appendChild(scriptEl)
                            scriptEl.onerror = err => send_error(err.message, err.stack)
                            scriptEls.push(scriptEl)
                            await done
                        }
                        send_ok()
                    } catch (e) {
                        send_error(e.message, e.stack);
                    }
                }

                if (action === 'catch_clicks') {
                    try {
                        const top_origin = ev.origin;
                        document.body.addEventListener('click', event => {
                            if (event.which !== 1) return;
                            if (event.metaKey || event.ctrlKey || event.shiftKey) return;
                            if (event.defaultPrevented) return;

                            // ensure target is a link
                            let el = event.target;
                            while (el && el.nodeName !== 'A') el = el.parentNode;
                            if (!el || el.nodeName !== 'A') return;

                            if (el.hasAttribute('download') || el.getAttribute('rel') === 'external' || el.target) return;

                            event.preventDefault();

                            if (el.href.startsWith(top_origin)) {
                                const url = new URL(el.href);
                                if (url.hash[0] === '#') {
                                    window.location.hash = url.hash;
                                    return;
                                }
                            }

                            window.open(el.href, '_blank');
                        });
                        send_ok();
                    } catch(e) {
                        send_error(e.message, e.stack);
                    }
                }

                if (action === "load_dependencies") {
                    try {
                        load_dependencies(ev.data.args);
                        send_ok()
                    }catch (e) {
                        send_error(e.message, e.stack);
                    }

                }
            }

            window.addEventListener('message', handle_message, false);

            window.onerror = function (msg, url, lineNo, columnNo, error) {
                if (msg.includes('module specifier “vue”')) {
                    // firefox only error, ignore
                    return false
                }
                try {
                    parent.postMessage({ action: 'error', value: error }, '*');
                } catch (e) {
                    parent.postMessage({ action: 'error', value: msg }, '*');
                }
            }

            window.addEventListener("unhandledrejection", event => {
                if (event.reason.message.includes('Cross-origin')) {
                    event.preventDefault()
                    return
                }
                try {
                    parent.postMessage({ action: 'unhandledrejection', value: event.reason }, '*');
                } catch (e) {
                    parent.postMessage({ action: 'unhandledrejection', value: event.reason.message }, '*');
                }
            });

            let previous = { level: null, args: null };

            ['clear', 'log', 'info', 'dir', 'warn', 'error', 'table'].forEach((level) => {
                const original = console[level];
                console[level] = (...args) => {
                    const msg = String(args[0])
                    if (
                        msg.includes('You are running a development build of Vue') ||
                        msg.includes('You are running the esm-bundler build of Vue')
                    ) {
                        return
                    }
                    const stringifiedArgs = stringify(args);
                    if (
                        previous.level === level &&
                        previous.args &&
                        previous.args === stringifiedArgs
                    ) {
                        parent.postMessage({ action: 'console', level, duplicate: true }, '*');
                    } else {
                        previous = { level, args: stringifiedArgs };

                        try {
                            parent.postMessage({ action: 'console', level, args }, '*');
                        } catch (err) {
                            parent.postMessage({ action: 'console', level, args: args.map(a => {
                                    return a instanceof Error ? a.message : String(a)
                                }) }, '*');
                        }
                    }

                    original(...args);
                }
            });

            [
                { method: 'group', action: 'console_group' },
                { method: 'groupEnd', action: 'console_group_end' },
                { method: 'groupCollapsed', action: 'console_group_collapsed' },
            ].forEach((group_action) => {
                const original = console[group_action.method];
                console[group_action.method] = (label) => {
                    parent.postMessage({ action: group_action.action, label }, '*');

                    original(label);
                };
            });

            const timers = new Map();
            const original_time = console.time;
            const original_timelog = console.timeLog;
            const original_timeend = console.timeEnd;

            console.time = (label = 'default') => {
                original_time(label);
                timers.set(label, performance.now());
            }
            console.timeLog = (label = 'default') => {
                original_timelog(label);
                const now = performance.now();
                if (timers.has(label)) {
                    parent.postMessage({ action: 'console', level: 'system-log', args: [`${label}: ${now - timers.get(label)}ms`] }, '*');
                } else {
                    parent.postMessage({ action: 'console', level: 'system-warn', args: [`Timer '${label}' does not exist`] }, '*');
                }
            }
            console.timeEnd = (label = 'default') => {
                original_timeend(label);
                const now = performance.now();
                if (timers.has(label)) {
                    parent.postMessage({ action: 'console', level: 'system-log', args: [`${label}: ${now - timers.get(label)}ms`] }, '*');
                } else {
                    parent.postMessage({ action: 'console', level: 'system-warn', args: [`Timer '${label}' does not exist`] }, '*');
                }
                timers.delete(label);
            };

            const original_assert = console.assert;
            console.assert = (condition, ...args) => {
                if (condition) {
                    const stack = new Error().stack;
                    parent.postMessage({ action: 'console', level: 'assert', args, stack }, '*');
                }
                original_assert(condition, ...args);
            };

            const counter = new Map();
            const original_count = console.count;
            const original_countreset = console.countReset;

            console.count = (label = 'default') => {
                counter.set(label, (counter.get(label) || 0) + 1);
                parent.postMessage({ action: 'console', level: 'system-log', args: `${label}: ${counter.get(label)}` }, '*');
                original_count(label);
            };

            console.countReset = (label = 'default') => {
                if (counter.has(label)) {
                    counter.set(label, 0);
                } else {
                    parent.postMessage({ action: 'console', level: 'system-warn', args: `Count for '${label}' does not exist` }, '*');
                }
                original_countreset(label);
            };

            const original_trace = console.trace;

            console.trace = (...args) => {
                const stack = new Error().stack;
                parent.postMessage({ action: 'console', level: 'trace', args, stack }, '*');
                original_trace(...args);
            };

            function stringify(args) {
                try {
                    return JSON.stringify(args);
                } catch (error) {
                    return null;
                }
            }
        })()
    </script>
</head>
<body>
    <div id="app"></div>
</body>
</html>
